package org.checkboo.hopefullyengine.sceneelements;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.LinkedList;

import org.checkboo.hopefullyengine.boundingvolumes.AxisAlignedBoundingBox;
import org.checkboo.hopefullyengine.boundingvolumes.BoundingVolume;
import org.checkboo.hopefullyengine.boundingvolumes.Frustum;
import org.checkboo.hopefullyengine.inversekinematics.Bone;
import org.checkboo.hopefullyengine.inversekinematics.Skeleton;
import org.checkboo.hopefullyengine.light.DirectionalLight;
import org.checkboo.hopefullyengine.light.Light;
import org.checkboo.hopefullyengine.light.PointLight;
import org.checkboo.hopefullyengine.light.SpotLight;
import org.checkboo.hopefullyengine.scenegraph.GeometryNode;
import org.checkboo.hopefullyengine.scenegraph.GroupNode;
import org.checkboo.hopefullyengine.scenegraph.LightNode;
import org.checkboo.hopefullyengine.scenegraph.Node;
import org.checkboo.hopefullyengine.scenegraph.SceneGraph;
import org.checkboo.hopefullyengine.text.Text;
import org.checkboo.hopefullyengine.text.TextManager;
import org.checkboo.math.Vector3f;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.ARBOcclusionQuery;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;

public class Renderer {

	private DisplayMode displayMode;
	private LinkedList<BaseObject> objects;
	private LinkedList<Light> lights;
	private GroupNode rootNode;
	private SceneGraph sceneGraph;
	private int i = 0;
	private int[] nums = {GL11.GL_LIGHT0,GL11.GL_LIGHT1,GL11.GL_LIGHT2,GL11.GL_LIGHT3,GL11.GL_LIGHT4,GL11.GL_LIGHT5,GL11.GL_LIGHT6,GL11.GL_LIGHT7};
	
	private int numObjects = 0;
	private long numVertices = 0;
	
	private Skeleton skeleton;

	public int getNumObjects() {
		return numObjects;
	}
	
	public long getNumVertices(){
		return numVertices;
	}

	public void setNumObjects(int numObjects) {
		this.numObjects = numObjects;
	}

	public Renderer(){
		
	}
	
	public LinkedList<BaseObject> getObjects() {
		return objects;
	}

	public void setObjects(LinkedList<BaseObject> objects) {
		this.objects = objects;
	}

	public LinkedList<Light> getLights() {
		return lights;
	}

	public void setLights(LinkedList<Light> lights) {
		this.lights = lights;
	}
	
	public void setRootNode(GroupNode root){
		this.sceneGraph = new SceneGraph(root);
		this.rootNode = root;
	}

	private void createWindow() throws Exception {
        Display.setFullscreen(false);
        DisplayMode d[] = Display.getAvailableDisplayModes();
        for (int i = 0; i < d.length; i++) {
            if (d[i].getWidth() == 1280
                && d[i].getHeight() == 720
                && d[i].getBitsPerPixel() == 24) {
                displayMode = d[i];
                break;
            }
        }
        //displayMode = d[1];
        Display.setDisplayMode(displayMode);
        Display.setTitle("test");
        Display.create();
    }
	
    public void init() throws Exception {
        createWindow();

        initGL();
    }

    private void initGL() {
    	GL11.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);              // Clear The Background Color To Black
        GL11.glClearDepth(1.0);                                 // Enables Clearing Of The Depth Buffer
        GL11.glDepthFunc(GL11.GL_LEQUAL);                       // The Type Of Depth Test To Do
        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);       // Select The Type Of Blending
        GL11.glShadeModel(GL11.GL_SMOOTH);                      // Enables Smooth Color Shading
        GL11.glEnable(GL11.GL_TEXTURE_2D);                      // Enable 2D Texture Mapping
        GL11.glMatrixMode(GL11.GL_PROJECTION);
        // Select The Projection Matrix
        GL11.glLoadIdentity(); // Reset The Projection Matrix

        // Calculate The Aspect Ratio Of The Window
        GLU.gluPerspective(45.0f,
                (float) displayMode.getWidth() / (float) displayMode.getHeight(),
                0.1f,100.0f);
        GL11.glMatrixMode(GL11.GL_MODELVIEW);

        GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_NICEST);

        
        GL11.glEnable(GL11.GL_LIGHTING);
        GL11.glCullFace(GL11.GL_BACK);
        GL11.glEnable(GL11.GL_CULL_FACE);
    }
    
    public void cleanup() {
        Display.destroy();
    }
    
    public void createLight(Light l){
    		if(i < 8){
    			GL11.glLight(nums[i], GL11.GL_DIFFUSE, l.getDiffuseBuffer());
    			GL11.glLight(nums[i], GL11.GL_SPECULAR, l.getSpecularBuffer());
    			GL11.glLight(nums[i], GL11.GL_AMBIENT, l.getAmbientBuffer());
    		if(l instanceof DirectionalLight){
    			DirectionalLight dir = (DirectionalLight) l;   
    			GL11.glLight(nums[i], GL11.GL_POSITION, dir.getDirectionBuffer());
    		}
    		if(l instanceof PointLight){
    			PointLight point = (PointLight) l;  
    			GL11.glLight(nums[i], GL11.GL_POSITION, point.getPositionBuffer());
    		}
    		if(l instanceof SpotLight){
    			SpotLight spot = (SpotLight) l;
    			GL11.glLight(nums[i], GL11.GL_SPOT_DIRECTION, spot.getDirectionBuffer());
    			GL11.glLight(nums[i], GL11.GL_POSITION, spot.getPositionBuffer());
    			GL11.glLightf(nums[i], GL11.GL_SPOT_CUTOFF, spot.getSpread());
    		}
    			GL11.glEnable(nums[i]);
    			i++;
    		}
    }
    
    public boolean render() {  
    	numVertices = 0;
    	numObjects = 0;
    	GL11.glDisable(GL11.GL_BLEND);
       
        GL11.glLoadIdentity();   
        GL11.glMatrixMode(GL11.GL_MODELVIEW);
        sceneGraph.compileGraph();
        
        sceneGraph.getVisibleObjects(getFrustum());
        
        checkQueries(rootNode);
        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);  
        //startTransform(rootNode);
        traverseGraph(rootNode);
        //endTransform();

        GL11.glColorMask(false, false, false, false);
        GL11.glDepthMask(false);
        setQueries(rootNode); 
        GL11.glColorMask(true, true, true, true);
        GL11.glDepthMask(true);
        
        GL11.glEnable(GL11.GL_BLEND);
   		GL11.glLoadIdentity();
   		for(Text t : TextManager.getInstance().getTexts())
   			t.render();
 
        i=0;
        
        try {
			Display.swapBuffers();
		} catch (LWJGLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		Display.update();
        return true;
    }
    
    public void setQueries(GroupNode node){
    	GeometryNode tmp;
    	for(Node n : node.getChildren()){
    		if(n.isRender()){
    		if(n instanceof GeometryNode){
    			tmp = (GeometryNode) n;
    				startTransform(n);
    				GL11.glDisable(GL11.GL_TEXTURE_2D);
    				ARBOcclusionQuery.glBeginQueryARB(ARBOcclusionQuery.GL_SAMPLES_PASSED_ARB, tmp.getOcclusionQueryPointer().get(0));
    				{
    				GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    				if(!tmp.isOccluded())
    					renderGeometry(((GeometryNode)n).getGeometry());
    				else
    					renderBound(((GeometryNode)n).getBoundingVolume());
    				}
    				ARBOcclusionQuery.glEndQueryARB(ARBOcclusionQuery.GL_SAMPLES_PASSED_ARB);
    				GL11.glEnable(GL11.GL_TEXTURE_2D);
    				endTransform();
    			}
    			if(n instanceof GroupNode){
    				setQueries((GroupNode)n);
    			}  		
    		}
    	}
    }
    
    public void checkQueries(GroupNode node){
    	GeometryNode tmp;
    	for(Node n : node.getChildren()){
    		if(n.isRender()){
    			if(n instanceof GeometryNode){
    				tmp = (GeometryNode) n;
    				if(tmp.getOcclusionQueryPointer().get(0) == -1)
    					ARBOcclusionQuery.glGenQueriesARB(tmp.getOcclusionQueryPointer());
    				else{
    					int fragCount = ARBOcclusionQuery.glGetQueryObjectuiARB(tmp.getOcclusionQueryPointer().get(0), ARBOcclusionQuery.GL_QUERY_RESULT_ARB);
    					if(fragCount == 0)
    						tmp.setOccluded(true);
    					else
    						tmp.setOccluded(false);
    				}
    			}
    			
    			if(n instanceof GroupNode){
    				checkQueries((GroupNode) n);
    			}  		
    		}
    	}
    }
    
    public void traverseGraph(GroupNode node){
    	for(Node n : node.getChildren()){
    		
    		if(n.isRender()){
    			
    			if(n instanceof GeometryNode){
    				if(!((GeometryNode) n).isOccluded()){
    					numObjects++;
    					numVertices += ((GeometryNode)n).getGeometry().getNumVertices();
    					startTransform(n);
    					renderGeometry(((GeometryNode)n).getGeometry());
    					/*if(null != ((GeometryNode)n).getGeometry().getSkeleton()){
    						this.skeleton = ((GeometryNode)n).getGeometry().getSkeleton();
    						GL11.glDisable(GL11.GL_TEXTURE_2D);
    						GL11.glDisable(GL11.GL_CULL_FACE);
    						drawBones(this.skeleton.getRoot());
    						GL11.glEnable(GL11.GL_TEXTURE_2D);
    						GL11.glEnable(GL11.GL_CULL_FACE);
    					}*/
    					endTransform();
    				}
    				//renderBound(((GeometryNode)n).getBoundingVolume());
    			}
    			if(n instanceof LightNode){
    				startTransform(n);
    				createLight(((LightNode)n).getLight());
    				endTransform();
    			}
    			if(n instanceof GroupNode){
    				traverseGraph((GroupNode)n);
    				//renderBound(((GroupNode)n).getBoundingVolume());
    			}
    		}    		
    	}
    }
    
    public Frustum getFrustum(){
    	Frustum frustum = new Frustum();
    	return frustum;
    }
    
    public void startTransform(Node node){
    	GL11.glPushMatrix();             
    	GL11.glLoadMatrix(node.getWorldTransform().toFloatBuffer());
    }
    
    public void endTransform(){
    	GL11.glPopMatrix();
    }
    
    public void renderGeometry(BaseObject geometry){
    	GL11.glBindTexture(GL11.GL_TEXTURE_2D, geometry.getTexture().getTexturePointer());
    	GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
    	GL11.glEnableClientState(GL11.GL_NORMAL_ARRAY);
    	GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
    	GL11.glVertexPointer (3, 0, geometry.getDisplayVerticesBuffer());
    	GL11.glNormalPointer(0, geometry.getDisplayVertexNormalsBuffer());
    	GL11.glTexCoordPointer(2, 0, geometry.getTexCoordsBuffer());
    	GL11.glDrawElements(GL11.GL_TRIANGLES, geometry.getIndexBuffer());
    	GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY);
    	GL11.glDisableClientState(GL11.GL_NORMAL_ARRAY);
    	GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
    	
    	//System.out.println("rendered");
    }
    
    public void renderBound(BoundingVolume bound){
    	GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
    	GL11.glDisable(GL11.GL_LIGHTING);
    	GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    	GL11.glDisable(GL11.GL_TEXTURE_2D);
    	GL11.glDisable(GL11.GL_CULL_FACE);
    	GL11.glPushMatrix();
    	GL11.glLoadIdentity();
    	GL11.glBegin(GL11.GL_TRIANGLES);
    	AxisAlignedBoundingBox aabb = (AxisAlignedBoundingBox)bound;
    
    	
    	GL11.glVertex3f(aabb.getV1().getX(), aabb.getV1().getY(), aabb.getV1().getZ());
    	GL11.glVertex3f(aabb.getV3().getX(), aabb.getV3().getY(), aabb.getV3().getZ());
    	GL11.glVertex3f(aabb.getV4().getX(), aabb.getV4().getY(), aabb.getV4().getZ());
    	
    	GL11.glVertex3f(aabb.getV4().getX(), aabb.getV4().getY(), aabb.getV4().getZ());
    	GL11.glVertex3f(aabb.getV2().getX(), aabb.getV2().getY(), aabb.getV2().getZ());
    	GL11.glVertex3f(aabb.getV1().getX(), aabb.getV1().getY(), aabb.getV1().getZ());

    	GL11.glVertex3f(aabb.getV1().getX(), aabb.getV1().getY(), aabb.getV1().getZ());
    	GL11.glVertex3f(aabb.getV5().getX(), aabb.getV5().getY(), aabb.getV5().getZ());
    	GL11.glVertex3f(aabb.getV6().getX(), aabb.getV6().getY(), aabb.getV6().getZ());
    	
    	GL11.glVertex3f(aabb.getV6().getX(), aabb.getV6().getY(), aabb.getV6().getZ());
    	GL11.glVertex3f(aabb.getV2().getX(), aabb.getV2().getY(), aabb.getV2().getZ());
    	GL11.glVertex3f(aabb.getV1().getX(), aabb.getV1().getY(), aabb.getV1().getZ());
    	
    	GL11.glVertex3f(aabb.getV8().getX(), aabb.getV8().getY(), aabb.getV8().getZ());
    	GL11.glVertex3f(aabb.getV6().getX(), aabb.getV6().getY(), aabb.getV6().getZ());
    	GL11.glVertex3f(aabb.getV5().getX(), aabb.getV5().getY(), aabb.getV5().getZ());
    	
    	GL11.glVertex3f(aabb.getV5().getX(), aabb.getV5().getY(), aabb.getV5().getZ());
    	GL11.glVertex3f(aabb.getV7().getX(), aabb.getV7().getY(), aabb.getV7().getZ());
    	GL11.glVertex3f(aabb.getV8().getX(), aabb.getV8().getY(), aabb.getV8().getZ());
    	
    	GL11.glVertex3f(aabb.getV3().getX(), aabb.getV3().getY(), aabb.getV3().getZ());
    	GL11.glVertex3f(aabb.getV4().getX(), aabb.getV4().getY(), aabb.getV4().getZ());
    	GL11.glVertex3f(aabb.getV8().getX(), aabb.getV8().getY(), aabb.getV8().getZ());
    	
    	GL11.glVertex3f(aabb.getV8().getX(), aabb.getV8().getY(), aabb.getV8().getZ());
    	GL11.glVertex3f(aabb.getV7().getX(), aabb.getV7().getY(), aabb.getV7().getZ());
    	GL11.glVertex3f(aabb.getV3().getX(), aabb.getV3().getY(), aabb.getV3().getZ());
    	
    	GL11.glVertex3f(aabb.getV4().getX(), aabb.getV4().getY(), aabb.getV4().getZ());
    	GL11.glVertex3f(aabb.getV2().getX(), aabb.getV2().getY(), aabb.getV2().getZ());
    	GL11.glVertex3f(aabb.getV6().getX(), aabb.getV6().getY(), aabb.getV6().getZ());
    	
    	GL11.glVertex3f(aabb.getV6().getX(), aabb.getV6().getY(), aabb.getV6().getZ());
    	GL11.glVertex3f(aabb.getV8().getX(), aabb.getV8().getY(), aabb.getV8().getZ());
    	GL11.glVertex3f(aabb.getV4().getX(), aabb.getV4().getY(), aabb.getV4().getZ());
    	
    	GL11.glVertex3f(aabb.getV7().getX(), aabb.getV7().getY(), aabb.getV7().getZ());
    	GL11.glVertex3f(aabb.getV5().getX(), aabb.getV5().getY(), aabb.getV5().getZ());
    	GL11.glVertex3f(aabb.getV1().getX(), aabb.getV1().getY(), aabb.getV1().getZ());
    	
    	GL11.glVertex3f(aabb.getV1().getX(), aabb.getV1().getY(), aabb.getV1().getZ());
    	GL11.glVertex3f(aabb.getV3().getX(), aabb.getV3().getY(), aabb.getV3().getZ());
    	GL11.glVertex3f(aabb.getV7().getX(), aabb.getV7().getY(), aabb.getV7().getZ());
    	
    	GL11.glEnd();
    	GL11.glPopMatrix();
    	GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
    	GL11.glEnable(GL11.GL_TEXTURE_2D);
    	GL11.glEnable(GL11.GL_CULL_FACE);
    	GL11.glEnable(GL11.GL_LIGHTING);
    }
    
    private void drawBones(Bone bone){
    	if(bone.getName().equalsIgnoreCase("eyeleft") || bone.getName().equalsIgnoreCase("eyeright") || bone.getName().equalsIgnoreCase("jaw")){
    	//System.out.println("drawing bone: "+bone.getName());
    	float angle = 0.0f;
    	Vector3f axis = new Vector3f();
    	bone.getDirection().toAngleAxis(angle, axis);
    	GL11.glPushMatrix();
    	//GL11.glMultMatrix(bone.getDefaultTransformInverse().toFloatBuffer());
    	GL11.glMultMatrix(bone.getCompleteTransform().toFloatBuffer());
    	//GL11.glLoadIdentity();
    	//GL11.glRotatef(angle, axis.getX(), axis.getY(), axis.getZ());
    	//GL11.glTranslatef(bone.getPosition().getX(), bone.getPosition().getY(), bone.getPosition().getZ());
    	GL11.glBegin(GL11.GL_QUADS);
    	GL11.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    		GL11.glVertex3f(-0.3f, +0.3f, +0.3f);
    		GL11.glVertex3f(+0.3f, +0.3f, +0.3f);
    		GL11.glVertex3f(+0.3f, -0.3f, +0.3f);
    		GL11.glVertex3f(-0.3f, -0.3f, +0.3f);
   
    		GL11.glVertex3f(-0.3f, +0.3f, -0.3f);
    		GL11.glVertex3f(+0.3f, +0.3f, -0.3f);
    		GL11.glVertex3f(+0.3f, -0.3f, -0.3f);
    		GL11.glVertex3f(-0.3f, -0.3f, -0.3f);
    		
    		GL11.glVertex3f(-0.3f, +0.3f, -0.3f);
    		GL11.glVertex3f(+0.3f, +0.3f, -0.3f);
    		GL11.glVertex3f(+0.3f, +0.3f, +0.3f);
    		GL11.glVertex3f(-0.3f, +0.3f, +0.3f);
    		
    		GL11.glVertex3f(-0.3f, -0.3f, -0.3f);
    		GL11.glVertex3f(+0.3f, -0.3f, -0.3f);
    		GL11.glVertex3f(+0.3f, -0.3f, +0.3f);
    		GL11.glVertex3f(-0.3f, -0.3f, +0.3f);
    		
    		GL11.glVertex3f(-0.3f, +0.3f, -0.3f);
    		GL11.glVertex3f(-0.3f, +0.3f, +0.3f);
    		GL11.glVertex3f(-0.3f, -0.3f, +0.3f);
    		GL11.glVertex3f(-0.3f, -0.3f, -0.3f);
    		
    		GL11.glVertex3f(+0.3f, +0.3f, +0.3f);
    		GL11.glVertex3f(+0.3f, +0.3f, -0.3f);
    		GL11.glVertex3f(+0.3f, -0.3f, -0.3f);
    		GL11.glVertex3f(+0.3f, -0.3f, +0.3f);
    	GL11.glEnd();
    	//GL11.glVertex3f(1.0f, 0.0f, -10.0f);
    	GL11.glPopMatrix();
    	}
    	for(Bone child : bone.getChildren()){
    		drawBones(child);
    	}
    }
    
    static public FloatBuffer getMousePosition(int mouseX, int mouseY)
    {
    	IntBuffer viewport = BufferUtils.createIntBuffer(16);
    	FloatBuffer modelview = BufferUtils.createFloatBuffer(16);
    	FloatBuffer projection = BufferUtils.createFloatBuffer(16);
    	FloatBuffer winZ = BufferUtils.createFloatBuffer(1);
    	float winX, winY;
    	FloatBuffer position = BufferUtils.createFloatBuffer(3);

    	GL11.glGetFloat( GL11.GL_MODELVIEW_MATRIX, modelview );
    	GL11.glGetFloat( GL11.GL_PROJECTION_MATRIX, projection );
    	GL11.glGetInteger( GL11.GL_VIEWPORT, viewport );

    	winX = (float)mouseX;
    	winY = (float)viewport.get(3) - (float)mouseY;

    	GL11.glReadPixels(mouseX, (int)winY, 1, 1, GL11.GL_DEPTH_COMPONENT, GL11.GL_FLOAT, winZ);

    	GLU.gluUnProject(winX, winY, winZ.get(), modelview, projection, viewport, position);

    	return position;
    }
}
